package com.my.framework.autoconfigure.redis;

import com.my.framework.redisConfig.RedisUtil;
import java.util.ArrayList;
import java.util.List;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.Codec;
import org.redisson.client.codec.StringCodec;
import org.redisson.config.ClusterServersConfig;
import org.redisson.config.Config;
import org.redisson.config.SentinelServersConfig;
import org.redisson.config.SingleServerConfig;
import org.redisson.spring.data.connection.RedissonConnectionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.ClassUtils;

@Configuration
@ConditionalOnClass({RedisUtil.class, Redisson.class, RedisOperations.class})
@AutoConfigureBefore({RedisAutoConfiguration.class})
@EnableConfigurationProperties({RedisProperties.class})
public class RedisClientAutoConfiguration {
  @Autowired
  private RedisProperties properties;
  
  @Bean(destroyMethod = "shutdown")
  @ConditionalOnMissingBean({RedissonClient.class})
  public RedissonClient redissonClient() {
    Config config = new Config();
    config.setLockWatchdogTimeout(this.properties.getLockWatchdogTimeout());
    try {
      config.setCodec((Codec)ClassUtils.forName(this.properties.getCodecClassName(), ClassUtils.getDefaultClassLoader()).newInstance());
    } catch (Exception e) {
      config.setCodec((Codec)new StringCodec());
    } 
    if (this.properties.getSentinel() != null) {
      RedisProperties.Sentinel sentinel = this.properties.getSentinel();
      ((SentinelServersConfig)((SentinelServersConfig)((SentinelServersConfig)((SentinelServersConfig)((SentinelServersConfig)((SentinelServersConfig)((SentinelServersConfig)((SentinelServersConfig)((SentinelServersConfig)config.useSentinelServers()
        .setMasterName(sentinel.getMaster())
        .addSentinelAddress(convert(sentinel.getNodes()))
        .setDatabase(sentinel.getDatabase())
        .setMasterConnectionPoolSize(this.properties.getConnectionPoolSize()))
        .setSlaveConnectionPoolSize(this.properties.getConnectionPoolSize()))
        .setMasterConnectionMinimumIdleSize(this.properties.getConnectionMinimumIdleSize()))
        .setSlaveConnectionMinimumIdleSize(this.properties.getConnectionMinimumIdleSize()))
        .setRetryAttempts(this.properties.getRetryAttempts()))
        .setRetryInterval(this.properties.getRetryInterval()))
        .setConnectTimeout(this.properties.getConnectTimeout()))
        .setTimeout(this.properties.getTimeout()))
        .setIdleConnectionTimeout(this.properties.getIdleConnectionTimeout()))
        .setPassword(this.properties.getPassword());
    } else if (this.properties.getCluster() != null) {
      RedisProperties.Cluster cluster = this.properties.getCluster();
      ((ClusterServersConfig)((ClusterServersConfig)((ClusterServersConfig)((ClusterServersConfig)((ClusterServersConfig)((ClusterServersConfig)((ClusterServersConfig)((ClusterServersConfig)((ClusterServersConfig)config.useClusterServers()
        .addNodeAddress(convert(cluster.getNodes()))
        .setMasterConnectionPoolSize(this.properties.getConnectionPoolSize()))
        .setSlaveConnectionPoolSize(this.properties.getConnectionPoolSize()))
        .setMasterConnectionMinimumIdleSize(this.properties.getConnectionMinimumIdleSize()))
        .setSlaveConnectionMinimumIdleSize(this.properties.getConnectionMinimumIdleSize()))
        .setRetryAttempts(this.properties.getRetryAttempts()))
        .setRetryInterval(this.properties.getRetryInterval()))
        .setConnectTimeout(this.properties.getConnectTimeout()))
        .setTimeout(this.properties.getTimeout()))
        .setIdleConnectionTimeout(this.properties.getIdleConnectionTimeout()))
        .setPassword(this.properties.getPassword());
    } else if (this.properties.getSingle() != null) {
      RedisProperties.Single single = this.properties.getSingle();
      ((SingleServerConfig)((SingleServerConfig)((SingleServerConfig)((SingleServerConfig)((SingleServerConfig)config.useSingleServer()
        .setAddress("redis://" + single.getHost() + ":" + single.getPort())
        .setDatabase(single.getDatabase())
        .setConnectionPoolSize(this.properties.getConnectionPoolSize())
        .setConnectionMinimumIdleSize(this.properties.getConnectionMinimumIdleSize())
        .setRetryAttempts(this.properties.getRetryAttempts()))
        .setRetryInterval(this.properties.getRetryInterval()))
        .setConnectTimeout(this.properties.getConnectTimeout()))
        .setTimeout(this.properties.getTimeout()))
        .setIdleConnectionTimeout(this.properties.getIdleConnectionTimeout()))
        .setPassword(this.properties.getPassword());
    } 
    return Redisson.create(config);
  }
  
  @Bean
  @ConditionalOnMissingBean({RedisConnectionFactory.class})
  public RedissonConnectionFactory redissonConnectionFactory(RedissonClient redissonClient) {
    return new RedissonConnectionFactory(redissonClient);
  }
  
  @Bean
  @ConditionalOnMissingBean({RedisTemplate.class})
  public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
    RedisTemplate<Object, Object> template = new RedisTemplate();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
  }
  
  @Bean
  @ConditionalOnMissingBean({StringRedisTemplate.class})
  public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
    StringRedisTemplate template = new StringRedisTemplate();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
  }
  
  @Bean
  @ConditionalOnMissingBean({RedisUtil.class})
  public RedisUtil redisUtil(RedissonClient redissonClient, StringRedisTemplate stringRedisTemplate) {
    return new RedisUtil(redissonClient, stringRedisTemplate);
  }
  
  private String[] convert(List<String> addresses) {
    List<String> nodes = new ArrayList<>();
    addresses.forEach(address -> {
          if (!address.startsWith("redis://") && !address.startsWith("rediss://")) {
            nodes.add("redis://" + address);
          } else {
            nodes.add(address);
          } 
        });
    return nodes.<String>toArray(new String[0]);
  }
}
